{
 "cells": [
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "ba0d9296-7fa6-4025-aedf-d2a19b05ff0d",
   "metadata": {},
   "source": [
    "# PaddleOCR with OpenVINO™\n",
    "\n",
    "This demo shows how to run PP-OCR model on OpenVINO natively. Instead of exporting the PaddlePaddle model to ONNX and then converting to the OpenVINO Intermediate Representation (OpenVINO IR) format with model conversion API, you can now read directly from the PaddlePaddle Model without any conversions. [PaddleOCR](https://github.com/PaddlePaddle/PaddleOCR) is an ultra-light OCR model trained with PaddlePaddle deep learning framework, that aims to create multilingual and practical OCR tools. \n",
    "\n",
    "The PaddleOCR pre-trained model used in the demo refers to the *\"Chinese and English ultra-lightweight PP-OCR model (9.4M)\"*. More open source pre-trained models can be downloaded at [PaddleOCR GitHub](https://github.com/PaddlePaddle/PaddleOCR) or [PaddleOCR Gitee](https://gitee.com/paddlepaddle/PaddleOCR). Working pipeline of the PaddleOCR is as follows:\n",
    "\n",
    "<img align='center' src= \"https://raw.githubusercontent.com/yoyowz/classification/master/images/pipeline.png\" alt=\"drawing\" width=\"1000\"/>\n",
    "\n",
    "> **NOTE**: To use this notebook with a webcam, you need to run the notebook on a computer with a webcam. If you run the notebook on a server, the webcam will not work. You can still do inference on a video file.\n",
    "\n",
    "\n",
    "#### Table of contents:\n",
    "\n",
    "- [Imports](#Imports)\n",
    "    - [Select inference device](#Select-inference-device)\n",
    "    - [Models for PaddleOCR](#Models-for-PaddleOCR)\n",
    "        - [Download the Model for Text **Detection**](#Download-the-Model-for-Text-**Detection**)\n",
    "        - [Load the Model for Text **Detection**](#Load-the-Model-for-Text-**Detection**)\n",
    "        - [Download the Model for Text **Recognition**](#Download-the-Model-for-Text-**Recognition**)\n",
    "        - [Load the Model for Text **Recognition** with Dynamic Shape](#Load-the-Model-for-Text-**Recognition**-with-Dynamic-Shape)\n",
    "    - [Preprocessing Image Functions for Text Detection and Recognition](#Preprocessing-Image-Functions-for-Text-Detection-and-Recognition)\n",
    "    - [Postprocessing Image for Text Detection](#Postprocessing-Image-for-Text-Detection)\n",
    "    - [Main Processing Function for PaddleOCR](#Main-Processing-Function-for-PaddleOCR)\n",
    "- [Run Live PaddleOCR with OpenVINO](#Run-Live-PaddleOCR-with-OpenVINO)\n",
    "\n",
    "\n",
    "### Installation Instructions\n",
    "\n",
    "This is a self-contained example that relies solely on its own code.\n",
    "\n",
    "We recommend  running the notebook in a virtual environment. You only need a Jupyter server to start.\n",
    "For details, please refer to [Installation Guide](https://github.com/openvinotoolkit/openvino_notebooks/blob/latest/README.md#-installation-guide).\n",
    "\n",
    "<img referrerpolicy=\"no-referrer-when-downgrade\" src=\"https://static.scarf.sh/a.png?x-pxid=5b5a4db0-7875-4bfb-bdbd-01698b5b1a77&file=notebooks/paddle-ocr-webcam/paddle-ocr-webcam.ipynb\" />\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "448c7e9e",
   "metadata": {},
   "outputs": [],
   "source": [
    "%pip install -q \"openvino>=2023.1.0\"\n",
    "%pip install -q \"paddlepaddle>=2.5.1\"\n",
    "%pip install -q \"pyclipper>=1.2.1\" \"shapely>=1.7.1\" opencv-python tqdm \"Pillow>=11.0\""
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "0e5a53f7-e1c5-4aca-879f-da2dd081b989",
   "metadata": {
    "tags": []
   },
   "source": [
    "## Imports\n",
    "[back to top ⬆️](#Table-of-contents:)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "f9486a04-b8bb-4bf5-9e13-845f2143a71b",
   "metadata": {},
   "outputs": [],
   "source": [
    "import cv2\n",
    "import numpy as np\n",
    "import paddle\n",
    "import math\n",
    "import time\n",
    "import collections\n",
    "from PIL import Image\n",
    "from pathlib import Path\n",
    "import tarfile\n",
    "\n",
    "import openvino as ov\n",
    "from IPython import display\n",
    "import copy"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "4b54398c",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Import local modules\n",
    "\n",
    "if not Path(\"./notebook_utils.py\").exists():\n",
    "    # Fetch `notebook_utils` module\n",
    "    import requests\n",
    "\n",
    "    r = requests.get(\n",
    "        url=\"https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/latest/utils/notebook_utils.py\",\n",
    "    )\n",
    "\n",
    "    open(\"notebook_utils.py\", \"w\").write(r.text)\n",
    "import notebook_utils as utils\n",
    "import pre_post_processing as processing\n",
    "\n",
    "# Read more about telemetry collection at https://github.com/openvinotoolkit/openvino_notebooks?tab=readme-ov-file#-telemetry\n",
    "from notebook_utils import collect_telemetry\n",
    "\n",
    "collect_telemetry(\"paddle-ocr-webcam.ipynb\")"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "ebe5b65c-ca61-4342-9e3b-475f76d1c096",
   "metadata": {},
   "source": [
    "### Select inference device\n",
    "[back to top ⬆️](#Table-of-contents:)\n",
    "\n",
    "select device from dropdown list for running inference using OpenVINO"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "236d4528-1963-4776-bcaa-c95bd94430b4",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "bb59bd230be14e308810204a222b63d8",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Dropdown(description='Device:', index=3, options=('CPU', 'GPU.0', 'GPU.1', 'AUTO'), value='AUTO')"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "device = utils.device_widget()\n",
    "\n",
    "device"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "ee4ea41d-18a8-4914-b367-d5717111d8e8",
   "metadata": {},
   "source": [
    "### Models for PaddleOCR\n",
    "[back to top ⬆️](#Table-of-contents:)\n",
    "\n",
    "PaddleOCR includes two parts of deep learning models, text detection and text recognition. Pre-trained models used in the demo are downloaded and stored in the \"model\" folder.\n",
    "\n",
    "Only a few lines of code are required to run the model. First, initialize the runtime for inference. Then, read the network architecture and model weights from the `.pdmodel` and `.pdiparams` files to load to CPU/GPU."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "789f3c2f-d692-458e-8ec9-b7c6e63e3c49",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Define the function to download text detection and recognition models from PaddleOCR resources.\n",
    "\n",
    "\n",
    "def run_model_download(model_url: str, model_file_path: Path) -> None:\n",
    "    \"\"\"\n",
    "    Download pre-trained models from PaddleOCR resources\n",
    "\n",
    "    Parameters:\n",
    "        model_url: url link to pre-trained models\n",
    "        model_file_path: file path to store the downloaded model\n",
    "    \"\"\"\n",
    "    archive_path = model_file_path.absolute().parent.parent / model_url.split(\"/\")[-1]\n",
    "    if model_file_path.is_file():\n",
    "        print(\"Model already exists\")\n",
    "    else:\n",
    "        # Download the model from the server, and untar it.\n",
    "        print(\"Downloading the pre-trained model... May take a while...\")\n",
    "\n",
    "        # Create a directory.\n",
    "        utils.download_file(model_url, archive_path.name, archive_path.parent)\n",
    "        print(\"Model Downloaded\")\n",
    "\n",
    "        file = tarfile.open(archive_path)\n",
    "        res = file.extractall(archive_path.parent)\n",
    "        file.close()\n",
    "        if not res:\n",
    "            print(f\"Model Extracted to {model_file_path}.\")\n",
    "        else:\n",
    "            print(\"Error Extracting the model. Please check the network.\")"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "e541150c-0f98-41c6-a97c-97acb26efd2f",
   "metadata": {},
   "source": [
    "#### Download the Model for Text **Detection**\n",
    "[back to top ⬆️](#Table-of-contents:)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "02fe27ea-0aaf-4ecb-bce2-858d70c84e93",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Downloading the pre-trained model... May take a while...\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "2efb22449fb94d1abbdf65939d468c68",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "/home/ea/work/openvino_notebooks_new_clone/openvino_notebooks/notebooks/paddle-ocr-webcam/model/ch_PP-OCRv3_de…"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Model Downloaded\n",
      "Model Extracted to model/ch_PP-OCRv3_det_infer/inference.pdmodel.\n"
     ]
    }
   ],
   "source": [
    "# A directory where the model will be downloaded.\n",
    "\n",
    "det_model_url = \"https://storage.openvinotoolkit.org/repositories/openvino_notebooks/models/paddle-ocr/ch_PP-OCRv3_det_infer.tar\"\n",
    "det_model_file_path = Path(\"model/ch_PP-OCRv3_det_infer/inference.pdmodel\")\n",
    "\n",
    "run_model_download(det_model_url, det_model_file_path)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "2f454531-81f0-4468-9867-3f9de9775aaf",
   "metadata": {},
   "source": [
    "#### Load the Model for Text **Detection**\n",
    "[back to top ⬆️](#Table-of-contents:)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "f9c5c83a-961c-4d98-8b20-5e96c8ef71f3",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Initialize OpenVINO Runtime for text detection.\n",
    "core = ov.Core()\n",
    "det_model = core.read_model(model=det_model_file_path)\n",
    "det_compiled_model = core.compile_model(model=det_model, device_name=device.value)\n",
    "\n",
    "# Get input and output nodes for text detection.\n",
    "det_input_layer = det_compiled_model.input(0)\n",
    "det_output_layer = det_compiled_model.output(0)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "5ec5c940-626c-4cf7-a90f-833200969846",
   "metadata": {},
   "source": [
    "#### Download the Model for Text **Recognition**\n",
    "[back to top ⬆️](#Table-of-contents:)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "89c0a07a-8186-47b5-ad95-f104a84d13d8",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Downloading the pre-trained model... May take a while...\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "f86690c9242b463794e65c8334bb6ce6",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "/home/ea/work/openvino_notebooks_new_clone/openvino_notebooks/notebooks/paddle-ocr-webcam/model/ch_PP-OCRv3_re…"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Model Downloaded\n",
      "Model Extracted to model/ch_PP-OCRv3_rec_infer/inference.pdmodel.\n"
     ]
    }
   ],
   "source": [
    "rec_model_url = \"https://storage.openvinotoolkit.org/repositories/openvino_notebooks/models/paddle-ocr/ch_PP-OCRv3_rec_infer.tar\"\n",
    "rec_model_file_path = Path(\"model/ch_PP-OCRv3_rec_infer/inference.pdmodel\")\n",
    "\n",
    "run_model_download(rec_model_url, rec_model_file_path)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "20155aeb-401a-4759-baee-dcb24a605ece",
   "metadata": {},
   "source": [
    "#### Load the Model for Text **Recognition** with Dynamic Shape\n",
    "[back to top ⬆️](#Table-of-contents:)\n"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "927c2017-33af-4449-a7a4-10dfea86c110",
   "metadata": {},
   "source": [
    "Input to text recognition model refers to detected bounding boxes with different image sizes, for example, dynamic input shapes. Hence:\n",
    "\n",
    "1. Input dimension with dynamic input shapes needs to be specified before loading text recognition model.\n",
    "2. Dynamic shape is specified by assigning -1 to the input dimension or by setting the upper bound of the input dimension using, for example, `Dimension(1, 512)`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "3d196913-6542-4177-87ab-c5aa1994f8e8",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Read the model and corresponding weights from a file.\n",
    "rec_model = core.read_model(model=rec_model_file_path)\n",
    "\n",
    "# Assign dynamic shapes to every input layer on the last dimension.\n",
    "for input_layer in rec_model.inputs:\n",
    "    input_shape = input_layer.partial_shape\n",
    "    input_shape[3] = -1\n",
    "    rec_model.reshape({input_layer: input_shape})\n",
    "\n",
    "rec_compiled_model = core.compile_model(model=rec_model, device_name=\"AUTO\")\n",
    "\n",
    "# Get input and output nodes.\n",
    "rec_input_layer = rec_compiled_model.input(0)\n",
    "rec_output_layer = rec_compiled_model.output(0)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "573a1a11-faec-41af-bf43-08b90d28cec3",
   "metadata": {},
   "source": [
    "### Preprocessing Image Functions for Text Detection and Recognition\n",
    "[back to top ⬆️](#Table-of-contents:)\n"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "fb3befa4-1cf8-4ac2-a5a0-e0e73498d755",
   "metadata": {},
   "source": [
    "Define preprocessing functions for text detection and recognition:\n",
    "1. Preprocessing for text detection: resize and normalize input images.\n",
    "2. Preprocessing for text recognition: resize and normalize detected box images to the same size (for example, `(3, 32, 320)` size for images with Chinese text) for easy batching in inference."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "93bc8364-109b-4a32-b12b-bcb85f23b38c",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Preprocess for text detection.\n",
    "def image_preprocess(input_image, size):\n",
    "    \"\"\"\n",
    "    Preprocess input image for text detection\n",
    "\n",
    "    Parameters:\n",
    "        input_image: input image\n",
    "        size: value for the image to be resized for text detection model\n",
    "    \"\"\"\n",
    "    img = cv2.resize(input_image, (size, size))\n",
    "    img = np.transpose(img, [2, 0, 1]) / 255\n",
    "    img = np.expand_dims(img, 0)\n",
    "    # NormalizeImage: {mean: [0.485, 0.456, 0.406], std: [0.229, 0.224, 0.225], is_scale: True}\n",
    "    img_mean = np.array([0.485, 0.456, 0.406]).reshape((3, 1, 1))\n",
    "    img_std = np.array([0.229, 0.224, 0.225]).reshape((3, 1, 1))\n",
    "    img -= img_mean\n",
    "    img /= img_std\n",
    "    return img.astype(np.float32)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "9329d709-14bc-45aa-a1d7-d0d6d608933b",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Preprocess for text recognition.\n",
    "def resize_norm_img(img, max_wh_ratio):\n",
    "    \"\"\"\n",
    "    Resize input image for text recognition\n",
    "\n",
    "    Parameters:\n",
    "        img: bounding box image from text detection\n",
    "        max_wh_ratio: value for the resizing for text recognition model\n",
    "    \"\"\"\n",
    "    rec_image_shape = [3, 48, 320]\n",
    "    imgC, imgH, imgW = rec_image_shape\n",
    "    assert imgC == img.shape[2]\n",
    "    character_type = \"ch\"\n",
    "    if character_type == \"ch\":\n",
    "        imgW = int((32 * max_wh_ratio))\n",
    "    h, w = img.shape[:2]\n",
    "    ratio = w / float(h)\n",
    "    if math.ceil(imgH * ratio) > imgW:\n",
    "        resized_w = imgW\n",
    "    else:\n",
    "        resized_w = int(math.ceil(imgH * ratio))\n",
    "    resized_image = cv2.resize(img, (resized_w, imgH))\n",
    "    resized_image = resized_image.astype(\"float32\")\n",
    "    resized_image = resized_image.transpose((2, 0, 1)) / 255\n",
    "    resized_image -= 0.5\n",
    "    resized_image /= 0.5\n",
    "    padding_im = np.zeros((imgC, imgH, imgW), dtype=np.float32)\n",
    "    padding_im[:, :, 0:resized_w] = resized_image\n",
    "    return padding_im\n",
    "\n",
    "\n",
    "def prep_for_rec(dt_boxes, frame):\n",
    "    \"\"\"\n",
    "    Preprocessing of the detected bounding boxes for text recognition\n",
    "\n",
    "    Parameters:\n",
    "        dt_boxes: detected bounding boxes from text detection\n",
    "        frame: original input frame\n",
    "    \"\"\"\n",
    "    ori_im = frame.copy()\n",
    "    img_crop_list = []\n",
    "    for bno in range(len(dt_boxes)):\n",
    "        tmp_box = copy.deepcopy(dt_boxes[bno])\n",
    "        img_crop = processing.get_rotate_crop_image(ori_im, tmp_box)\n",
    "        img_crop_list.append(img_crop)\n",
    "\n",
    "    img_num = len(img_crop_list)\n",
    "    # Calculate the aspect ratio of all text bars.\n",
    "    width_list = []\n",
    "    for img in img_crop_list:\n",
    "        width_list.append(img.shape[1] / float(img.shape[0]))\n",
    "\n",
    "    # Sorting can speed up the recognition process.\n",
    "    indices = np.argsort(np.array(width_list))\n",
    "    return img_crop_list, img_num, indices\n",
    "\n",
    "\n",
    "def batch_text_box(img_crop_list, img_num, indices, beg_img_no, batch_num):\n",
    "    \"\"\"\n",
    "    Batch for text recognition\n",
    "\n",
    "    Parameters:\n",
    "        img_crop_list: processed detected bounding box images\n",
    "        img_num: number of bounding boxes from text detection\n",
    "        indices: sorting for bounding boxes to speed up text recognition\n",
    "        beg_img_no: the beginning number of bounding boxes for each batch of text recognition inference\n",
    "        batch_num: number of images for each batch\n",
    "    \"\"\"\n",
    "    norm_img_batch = []\n",
    "    max_wh_ratio = 0\n",
    "    end_img_no = min(img_num, beg_img_no + batch_num)\n",
    "    for ino in range(beg_img_no, end_img_no):\n",
    "        h, w = img_crop_list[indices[ino]].shape[0:2]\n",
    "        wh_ratio = w * 1.0 / h\n",
    "        max_wh_ratio = max(max_wh_ratio, wh_ratio)\n",
    "    for ino in range(beg_img_no, end_img_no):\n",
    "        norm_img = resize_norm_img(img_crop_list[indices[ino]], max_wh_ratio)\n",
    "        norm_img = norm_img[np.newaxis, :]\n",
    "        norm_img_batch.append(norm_img)\n",
    "\n",
    "    norm_img_batch = np.concatenate(norm_img_batch)\n",
    "    norm_img_batch = norm_img_batch.copy()\n",
    "    return norm_img_batch"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "5ee36029-eabd-4ffc-ab45-ac293b62f32b",
   "metadata": {},
   "source": [
    "### Postprocessing Image for Text Detection\n",
    "[back to top ⬆️](#Table-of-contents:)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "409df7bc-2236-47ef-8645-48e9e40d05f1",
   "metadata": {},
   "outputs": [],
   "source": [
    "def post_processing_detection(frame, det_results):\n",
    "    \"\"\"\n",
    "    Postprocess the results from text detection into bounding boxes\n",
    "\n",
    "    Parameters:\n",
    "        frame: input image\n",
    "        det_results: inference results from text detection model\n",
    "    \"\"\"\n",
    "    ori_im = frame.copy()\n",
    "    data = {\"image\": frame}\n",
    "    data_resize = processing.DetResizeForTest(data)\n",
    "    data_list = []\n",
    "    keep_keys = [\"image\", \"shape\"]\n",
    "    for key in keep_keys:\n",
    "        data_list.append(data_resize[key])\n",
    "    img, shape_list = data_list\n",
    "\n",
    "    shape_list = np.expand_dims(shape_list, axis=0)\n",
    "    pred = det_results[0]\n",
    "    if isinstance(pred, paddle.Tensor):\n",
    "        pred = pred.numpy()\n",
    "    segmentation = pred > 0.3\n",
    "\n",
    "    boxes_batch = []\n",
    "    for batch_index in range(pred.shape[0]):\n",
    "        src_h, src_w, ratio_h, ratio_w = shape_list[batch_index]\n",
    "        mask = segmentation[batch_index]\n",
    "        boxes, scores = processing.boxes_from_bitmap(pred[batch_index], mask, src_w, src_h)\n",
    "        boxes_batch.append({\"points\": boxes})\n",
    "    post_result = boxes_batch\n",
    "    dt_boxes = post_result[0][\"points\"]\n",
    "    dt_boxes = processing.filter_tag_det_res(dt_boxes, ori_im.shape)\n",
    "    return dt_boxes"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "01d3695c-42c3-43d3-8472-9f16913182bf",
   "metadata": {},
   "source": [
    "### Main Processing Function for PaddleOCR\n",
    "[back to top ⬆️](#Table-of-contents:)\n"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "17ce8c76-3ea5-402c-b820-a403bf12cc05",
   "metadata": {},
   "source": [
    "Run `paddleOCR` function in different operations, either a webcam or a video file. See the list of procedures below:\n",
    "\n",
    "1. Create a video player to play with target fps (`utils.VideoPlayer`).\n",
    "2. Prepare a set of frames for text detection and recognition.\n",
    "3. Run AI inference for both text detection and recognition.\n",
    "4. Visualize the results."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "874b545f",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "3d58d452a4f14ea6b84f21ed7c7bd904",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "fonts/simfang.ttf:   0%|          | 0.00/10.1M [00:00<?, ?B/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "5dfb4906cb9c4bdebc5c34a5ef0c26fa",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "fonts/ppocr_keys_v1.txt:   0%|          | 0.00/17.3k [00:00<?, ?B/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Download font and a character dictionary for printing OCR results.\n",
    "font_path = Path(\"fonts/simfang.ttf\")\n",
    "character_dictionary_path = Path(\"fonts/ppocr_keys_v1.txt\")\n",
    "\n",
    "if not font_path.exists():\n",
    "    utils.download_file(\n",
    "        url=\"https://raw.githubusercontent.com/Halfish/lstm-ctc-ocr/master/fonts/simfang.ttf\",\n",
    "        directory=\"fonts\",\n",
    "    )\n",
    "if not character_dictionary_path.exists():\n",
    "    utils.download_file(\n",
    "        url=\"https://raw.githubusercontent.com/WenmuZhou/PytorchOCR/master/torchocr/datasets/alphabets/ppocr_keys_v1.txt\",\n",
    "        directory=\"fonts\",\n",
    "    )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "de5b68ee-bd25-4dd8-9e87-3fe6971c6e64",
   "metadata": {},
   "outputs": [],
   "source": [
    "def run_paddle_ocr(source=0, flip=False, use_popup=False, skip_first_frames=0):\n",
    "    \"\"\"\n",
    "    Main function to run the paddleOCR inference:\n",
    "    1. Create a video player to play with target fps (utils.VideoPlayer).\n",
    "    2. Prepare a set of frames for text detection and recognition.\n",
    "    3. Run AI inference for both text detection and recognition.\n",
    "    4. Visualize the results.\n",
    "\n",
    "    Parameters:\n",
    "        source: The webcam number to feed the video stream with primary webcam set to \"0\", or the video path.\n",
    "        flip: To be used by VideoPlayer function for flipping capture image.\n",
    "        use_popup: False for showing encoded frames over this notebook, True for creating a popup window.\n",
    "        skip_first_frames: Number of frames to skip at the beginning of the video.\n",
    "    \"\"\"\n",
    "    # Create a video player to play with target fps.\n",
    "    player = None\n",
    "    try:\n",
    "        player = utils.VideoPlayer(source=source, flip=flip, fps=30, skip_first_frames=skip_first_frames)\n",
    "        # Start video capturing.\n",
    "        player.start()\n",
    "        if use_popup:\n",
    "            title = \"Press ESC to Exit\"\n",
    "            cv2.namedWindow(winname=title, flags=cv2.WINDOW_GUI_NORMAL | cv2.WINDOW_AUTOSIZE)\n",
    "\n",
    "        processing_times = collections.deque()\n",
    "        while True:\n",
    "            # Grab the frame.\n",
    "            frame = player.next()\n",
    "            if frame is None:\n",
    "                print(\"Source ended\")\n",
    "                break\n",
    "            # If the frame is larger than full HD, reduce size to improve the performance.\n",
    "            scale = 1280 / max(frame.shape)\n",
    "            if scale < 1:\n",
    "                frame = cv2.resize(\n",
    "                    src=frame,\n",
    "                    dsize=None,\n",
    "                    fx=scale,\n",
    "                    fy=scale,\n",
    "                    interpolation=cv2.INTER_AREA,\n",
    "                )\n",
    "            # Preprocess the image for text detection.\n",
    "            test_image = image_preprocess(frame, 640)\n",
    "\n",
    "            # Measure processing time for text detection.\n",
    "            start_time = time.time()\n",
    "            # Perform the inference step.\n",
    "            det_results = det_compiled_model([test_image])[det_output_layer]\n",
    "            stop_time = time.time()\n",
    "\n",
    "            # Postprocessing for Paddle Detection.\n",
    "            dt_boxes = post_processing_detection(frame, det_results)\n",
    "\n",
    "            processing_times.append(stop_time - start_time)\n",
    "            # Use processing times from last 200 frames.\n",
    "            if len(processing_times) > 200:\n",
    "                processing_times.popleft()\n",
    "            processing_time_det = np.mean(processing_times) * 1000\n",
    "\n",
    "            # Preprocess detection results for recognition.\n",
    "            dt_boxes = processing.sorted_boxes(dt_boxes)\n",
    "            batch_num = 6\n",
    "            img_crop_list, img_num, indices = prep_for_rec(dt_boxes, frame)\n",
    "\n",
    "            # For storing recognition results, include two parts:\n",
    "            # txts are the recognized text results, scores are the recognition confidence level.\n",
    "            rec_res = [[\"\", 0.0]] * img_num\n",
    "            txts = []\n",
    "            scores = []\n",
    "\n",
    "            for beg_img_no in range(0, img_num, batch_num):\n",
    "                # Recognition starts from here.\n",
    "                norm_img_batch = batch_text_box(img_crop_list, img_num, indices, beg_img_no, batch_num)\n",
    "\n",
    "                # Run inference for text recognition.\n",
    "                rec_results = rec_compiled_model([norm_img_batch])[rec_output_layer]\n",
    "\n",
    "                # Postprocessing recognition results.\n",
    "                postprocess_op = processing.build_post_process(processing.postprocess_params)\n",
    "                rec_result = postprocess_op(rec_results)\n",
    "                for rno in range(len(rec_result)):\n",
    "                    rec_res[indices[beg_img_no + rno]] = rec_result[rno]\n",
    "                if rec_res:\n",
    "                    txts = [rec_res[i][0] for i in range(len(rec_res))]\n",
    "                    scores = [rec_res[i][1] for i in range(len(rec_res))]\n",
    "\n",
    "            image = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))\n",
    "            boxes = dt_boxes\n",
    "            # Draw text recognition results beside the image.\n",
    "            draw_img = processing.draw_ocr_box_txt(image, boxes, txts, scores, drop_score=0.5, font_path=str(font_path))\n",
    "\n",
    "            # Visualize the PaddleOCR results.\n",
    "            f_height, f_width = draw_img.shape[:2]\n",
    "            fps = 1000 / processing_time_det\n",
    "            cv2.putText(\n",
    "                img=draw_img,\n",
    "                text=f\"Inference time: {processing_time_det:.1f}ms ({fps:.1f} FPS)\",\n",
    "                org=(20, 40),\n",
    "                fontFace=cv2.FONT_HERSHEY_COMPLEX,\n",
    "                fontScale=f_width / 1000,\n",
    "                color=(0, 0, 255),\n",
    "                thickness=1,\n",
    "                lineType=cv2.LINE_AA,\n",
    "            )\n",
    "\n",
    "            # Use this workaround if there is flickering.\n",
    "            if use_popup:\n",
    "                draw_img = cv2.cvtColor(draw_img, cv2.COLOR_RGB2BGR)\n",
    "                cv2.imshow(winname=title, mat=draw_img)\n",
    "                key = cv2.waitKey(1)\n",
    "                # escape = 27\n",
    "                if key == 27:\n",
    "                    break\n",
    "            else:\n",
    "                # Encode numpy array to jpg.\n",
    "                draw_img = cv2.cvtColor(draw_img, cv2.COLOR_RGB2BGR)\n",
    "                _, encoded_img = cv2.imencode(ext=\".jpg\", img=draw_img, params=[cv2.IMWRITE_JPEG_QUALITY, 100])\n",
    "                # Create an IPython image.\n",
    "                i = display.Image(data=encoded_img)\n",
    "                # Display the image in this notebook.\n",
    "                display.clear_output(wait=True)\n",
    "                display.display(i)\n",
    "\n",
    "    # ctrl-c\n",
    "    except KeyboardInterrupt:\n",
    "        print(\"Interrupted\")\n",
    "    # any different error\n",
    "    except RuntimeError as e:\n",
    "        print(e)\n",
    "    finally:\n",
    "        if player is not None:\n",
    "            # Stop capturing.\n",
    "            player.stop()\n",
    "        if use_popup:\n",
    "            cv2.destroyAllWindows()"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "92f8855f-418a-4bda-8799-0953dda895c5",
   "metadata": {},
   "source": [
    "## Run Live PaddleOCR with OpenVINO\n",
    "[back to top ⬆️](#Table-of-contents:)\n"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "7642697d-d000-4a10-8e7b-2a519cf9e687",
   "metadata": {},
   "source": [
    "Use a webcam as the video input. By default, the primary webcam is set with `source=0`. If you have multiple webcams, each one will be assigned a consecutive number starting at 0. Set `flip=True` when using a front-facing camera. Some web browsers, especially Mozilla Firefox, may cause flickering. If you experience flickering, set `use_popup=True`. \n",
    "\n",
    "> **NOTE**: Popup mode may not work if you run this notebook on a remote computer.\n",
    "\n",
    "If you do not have a webcam, you can still run this demo with a video file. Any [format supported by OpenCV](https://docs.opencv.org/4.5.1/dd/d43/tutorial_py_video_display.html) will work.\n",
    "\n",
    "Run live PaddleOCR:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "dc274952-19aa-480d-ba50-a1146a89771b",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "USE_WEBCAM = False\n",
    "\n",
    "cam_id = 0\n",
    "video_file = Path(\"test.mp4\")\n",
    "\n",
    "if not USE_WEBCAM and not video_file.exists():\n",
    "    utils.download_file(\"https://raw.githubusercontent.com/yoyowz/classification/master/images/test.mp4\")\n",
    "\n",
    "source = cam_id if USE_WEBCAM else video_file\n",
    "\n",
    "run_paddle_ocr(source, flip=False, use_popup=False)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "bb283f2d",
   "metadata": {
    "test_replace": {
     "# %pip uninstall": "%pip uninstall"
    }
   },
   "outputs": [],
   "source": [
    "# Cleanup\n",
    "# %pip uninstall -y -q paddlepaddle"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.10"
  },
  "openvino_notebooks": {
   "imageUrl": "https://github.com/openvinotoolkit/openvino_notebooks/blob/latest/notebooks/paddle-ocr-webcam/paddle-ocr-webcam.gif?raw=true",
   "tags": {
    "categories": [
     "Live Demos"
    ],
    "libraries": [],
    "other": [],
    "tasks": [
     "Video-to-Text"
    ]
   }
  },
  "vscode": {
   "interpreter": {
    "hash": "cec18e25feb9469b5ff1085a8097bdcd86db6a4ac301d6aeff87d0f3e7ce4ca5"
   }
  },
  "widgets": {
   "application/vnd.jupyter.widget-state+json": {
    "state": {
     "09dc880d525e4201a36d92d2c1e499fe": {
      "model_module": "@jupyter-widgets/controls",
      "model_module_version": "2.0.0",
      "model_name": "HTMLStyleModel",
      "state": {
       "description_width": "",
       "font_size": null,
       "text_color": null
      }
     },
     "0a28747301ac4a9a972d050734322e87": {
      "model_module": "@jupyter-widgets/controls",
      "model_module_version": "2.0.0",
      "model_name": "HTMLModel",
      "state": {
       "layout": "IPY_MODEL_b557e7d649a348e4b31e5e6427b99246",
       "style": "IPY_MODEL_09dc880d525e4201a36d92d2c1e499fe",
       "value": " 32.1k/? [00:00&lt;00:00, 499kB/s]"
      }
     },
     "0bfb46a2b88548369925863e9038270e": {
      "model_module": "@jupyter-widgets/controls",
      "model_module_version": "2.0.0",
      "model_name": "FloatProgressModel",
      "state": {
       "bar_style": "success",
       "layout": "IPY_MODEL_ee15278fd6d24269b98ce6d85b4c4fe2",
       "max": 11909120,
       "style": "IPY_MODEL_3d1160c9c5224e3492fa9da4eb6d3fd3",
       "value": 11909120
      }
     },
     "0e998f223a0d44d8842dc51ea6478382": {
      "model_module": "@jupyter-widgets/controls",
      "model_module_version": "2.0.0",
      "model_name": "HTMLModel",
      "state": {
       "layout": "IPY_MODEL_74146ff5c5144092a5f87d75453fe711",
       "style": "IPY_MODEL_f374ed656ddf4d8b9eca24b67cf9c5f1",
       "value": " 10.1M/10.1M [00:01&lt;00:00, 15.1MB/s]"
      }
     },
     "0ea972ed8b7b4529b6930d8b8ef9293c": {
      "model_module": "@jupyter-widgets/base",
      "model_module_version": "2.0.0",
      "model_name": "LayoutModel",
      "state": {}
     },
     "0f0d0c07a3024ea69b4295ef45b21fdf": {
      "model_module": "@jupyter-widgets/base",
      "model_module_version": "2.0.0",
      "model_name": "LayoutModel",
      "state": {}
     },
     "1041bd563ce3497eacf0a659dc543aed": {
      "model_module": "@jupyter-widgets/base",
      "model_module_version": "2.0.0",
      "model_name": "LayoutModel",
      "state": {}
     },
     "135b0d19c276461ea52c1f36deb1ba78": {
      "model_module": "@jupyter-widgets/controls",
      "model_module_version": "2.0.0",
      "model_name": "HTMLModel",
      "state": {
       "layout": "IPY_MODEL_1041bd563ce3497eacf0a659dc543aed",
       "style": "IPY_MODEL_a8d0f0f9dac04e4ca93ab6ef51bdc12e",
       "value": " 11.4M/11.4M [00:01&lt;00:00, 15.0MB/s]"
      }
     },
     "21162005d2944e12b340e2313e797345": {
      "model_module": "@jupyter-widgets/base",
      "model_module_version": "2.0.0",
      "model_name": "LayoutModel",
      "state": {}
     },
     "2efb22449fb94d1abbdf65939d468c68": {
      "model_module": "@jupyter-widgets/controls",
      "model_module_version": "2.0.0",
      "model_name": "HBoxModel",
      "state": {
       "children": [
        "IPY_MODEL_7786e838dd8247f3b8324fb9ece68a5e",
        "IPY_MODEL_ba3efa1a96204956ae4071e5c91b24d2",
        "IPY_MODEL_cfdf93d4c0bd42309564153842c9fcd5"
       ],
       "layout": "IPY_MODEL_5c757fe8c35c470dbf3f5bbec380ec09"
      }
     },
     "301b0ea63e8241619b9bc6ec953af1cb": {
      "model_module": "@jupyter-widgets/controls",
      "model_module_version": "2.0.0",
      "model_name": "HTMLStyleModel",
      "state": {
       "description_width": "",
       "font_size": null,
       "text_color": null
      }
     },
     "3d1160c9c5224e3492fa9da4eb6d3fd3": {
      "model_module": "@jupyter-widgets/controls",
      "model_module_version": "2.0.0",
      "model_name": "ProgressStyleModel",
      "state": {
       "description_width": ""
      }
     },
     "3d58d452a4f14ea6b84f21ed7c7bd904": {
      "model_module": "@jupyter-widgets/controls",
      "model_module_version": "2.0.0",
      "model_name": "HBoxModel",
      "state": {
       "children": [
        "IPY_MODEL_dc87dc90f01a4c638216e0a846d4d0e3",
        "IPY_MODEL_abf2f8c455dd470184070d19b50bf624",
        "IPY_MODEL_0e998f223a0d44d8842dc51ea6478382"
       ],
       "layout": "IPY_MODEL_5f6052f6186645229e9a4fbd35e06b81"
      }
     },
     "429047e7016c454ea766b9620485d9a5": {
      "model_module": "@jupyter-widgets/base",
      "model_module_version": "2.0.0",
      "model_name": "LayoutModel",
      "state": {}
     },
     "485fcd3e8bc74845b279c4a06f7f30fa": {
      "model_module": "@jupyter-widgets/controls",
      "model_module_version": "2.0.0",
      "model_name": "ProgressStyleModel",
      "state": {
       "description_width": ""
      }
     },
     "4b918543b8254652b8ed99453ab7a4a6": {
      "model_module": "@jupyter-widgets/controls",
      "model_module_version": "2.0.0",
      "model_name": "DescriptionStyleModel",
      "state": {
       "description_width": ""
      }
     },
     "4efd0e6af9e142b18553a0b801d561fd": {
      "model_module": "@jupyter-widgets/controls",
      "model_module_version": "2.0.0",
      "model_name": "HTMLModel",
      "state": {
       "layout": "IPY_MODEL_544faeb3aeeb4a2090402f779968e45c",
       "style": "IPY_MODEL_d48d586016de46649a3f817d353cc3c7",
       "value": "fonts/ppocr_keys_v1.txt: "
      }
     },
     "544faeb3aeeb4a2090402f779968e45c": {
      "model_module": "@jupyter-widgets/base",
      "model_module_version": "2.0.0",
      "model_name": "LayoutModel",
      "state": {}
     },
     "55820c6da0a24ee18b27e2f7c411154c": {
      "model_module": "@jupyter-widgets/base",
      "model_module_version": "2.0.0",
      "model_name": "LayoutModel",
      "state": {}
     },
     "5c757fe8c35c470dbf3f5bbec380ec09": {
      "model_module": "@jupyter-widgets/base",
      "model_module_version": "2.0.0",
      "model_name": "LayoutModel",
      "state": {}
     },
     "5dfb4906cb9c4bdebc5c34a5ef0c26fa": {
      "model_module": "@jupyter-widgets/controls",
      "model_module_version": "2.0.0",
      "model_name": "HBoxModel",
      "state": {
       "children": [
        "IPY_MODEL_4efd0e6af9e142b18553a0b801d561fd",
        "IPY_MODEL_a5a1cb171208422889c96fc104a7e834",
        "IPY_MODEL_0a28747301ac4a9a972d050734322e87"
       ],
       "layout": "IPY_MODEL_55820c6da0a24ee18b27e2f7c411154c"
      }
     },
     "5ee7712b159e4a079d3e0c279bbef4b7": {
      "model_module": "@jupyter-widgets/base",
      "model_module_version": "2.0.0",
      "model_name": "LayoutModel",
      "state": {}
     },
     "5f6052f6186645229e9a4fbd35e06b81": {
      "model_module": "@jupyter-widgets/base",
      "model_module_version": "2.0.0",
      "model_name": "LayoutModel",
      "state": {}
     },
     "672709e6bc2849b19c937ae969848199": {
      "model_module": "@jupyter-widgets/controls",
      "model_module_version": "2.0.0",
      "model_name": "HTMLStyleModel",
      "state": {
       "description_width": "",
       "font_size": null,
       "text_color": null
      }
     },
     "74146ff5c5144092a5f87d75453fe711": {
      "model_module": "@jupyter-widgets/base",
      "model_module_version": "2.0.0",
      "model_name": "LayoutModel",
      "state": {}
     },
     "7786e838dd8247f3b8324fb9ece68a5e": {
      "model_module": "@jupyter-widgets/controls",
      "model_module_version": "2.0.0",
      "model_name": "HTMLModel",
      "state": {
       "layout": "IPY_MODEL_e03b677c6e24470d8cee9d64dc2c69a3",
       "style": "IPY_MODEL_301b0ea63e8241619b9bc6ec953af1cb",
       "value": "/home/ea/work/openvino_notebooks_new_clone/openvino_notebooks/notebooks/paddle-ocr-webcam/model/ch_PP-OCRv3_det_infer.tar: 100%"
      }
     },
     "8a8d0084847c4685bf4a701be58c46bd": {
      "model_module": "@jupyter-widgets/controls",
      "model_module_version": "2.0.0",
      "model_name": "HTMLStyleModel",
      "state": {
       "description_width": "",
       "font_size": null,
       "text_color": null
      }
     },
     "964a26ddbd404e18b94b65a0b3cd0316": {
      "model_module": "@jupyter-widgets/controls",
      "model_module_version": "2.0.0",
      "model_name": "HTMLModel",
      "state": {
       "layout": "IPY_MODEL_429047e7016c454ea766b9620485d9a5",
       "style": "IPY_MODEL_b08cd0f57ce14144a9909c37aa232ca7",
       "value": "/home/ea/work/openvino_notebooks_new_clone/openvino_notebooks/notebooks/paddle-ocr-webcam/model/ch_PP-OCRv3_rec_infer.tar: 100%"
      }
     },
     "a5a1cb171208422889c96fc104a7e834": {
      "model_module": "@jupyter-widgets/controls",
      "model_module_version": "2.0.0",
      "model_name": "FloatProgressModel",
      "state": {
       "bar_style": "success",
       "layout": "IPY_MODEL_ab0311abda7c4cf68347cdb6014efe52",
       "max": 17673,
       "style": "IPY_MODEL_dfee1d76cb634c8ba65009e1b92ee02a",
       "value": 17673
      }
     },
     "a8d0f0f9dac04e4ca93ab6ef51bdc12e": {
      "model_module": "@jupyter-widgets/controls",
      "model_module_version": "2.0.0",
      "model_name": "HTMLStyleModel",
      "state": {
       "description_width": "",
       "font_size": null,
       "text_color": null
      }
     },
     "ab0311abda7c4cf68347cdb6014efe52": {
      "model_module": "@jupyter-widgets/base",
      "model_module_version": "2.0.0",
      "model_name": "LayoutModel",
      "state": {}
     },
     "abf2f8c455dd470184070d19b50bf624": {
      "model_module": "@jupyter-widgets/controls",
      "model_module_version": "2.0.0",
      "model_name": "FloatProgressModel",
      "state": {
       "bar_style": "success",
       "layout": "IPY_MODEL_b4253bd1fb9945d980240440c39f40cc",
       "max": 10576012,
       "style": "IPY_MODEL_db8f8014959541348463652b4e001e87",
       "value": 10576012
      }
     },
     "b08cd0f57ce14144a9909c37aa232ca7": {
      "model_module": "@jupyter-widgets/controls",
      "model_module_version": "2.0.0",
      "model_name": "HTMLStyleModel",
      "state": {
       "description_width": "",
       "font_size": null,
       "text_color": null
      }
     },
     "b4253bd1fb9945d980240440c39f40cc": {
      "model_module": "@jupyter-widgets/base",
      "model_module_version": "2.0.0",
      "model_name": "LayoutModel",
      "state": {}
     },
     "b557e7d649a348e4b31e5e6427b99246": {
      "model_module": "@jupyter-widgets/base",
      "model_module_version": "2.0.0",
      "model_name": "LayoutModel",
      "state": {}
     },
     "ba3efa1a96204956ae4071e5c91b24d2": {
      "model_module": "@jupyter-widgets/controls",
      "model_module_version": "2.0.0",
      "model_name": "FloatProgressModel",
      "state": {
       "bar_style": "success",
       "layout": "IPY_MODEL_0f0d0c07a3024ea69b4295ef45b21fdf",
       "max": 3829760,
       "style": "IPY_MODEL_485fcd3e8bc74845b279c4a06f7f30fa",
       "value": 3829760
      }
     },
     "bb59bd230be14e308810204a222b63d8": {
      "model_module": "@jupyter-widgets/controls",
      "model_module_version": "2.0.0",
      "model_name": "DropdownModel",
      "state": {
       "_options_labels": [
        "CPU",
        "GPU.0",
        "GPU.1",
        "AUTO"
       ],
       "description": "Device:",
       "index": 3,
       "layout": "IPY_MODEL_5ee7712b159e4a079d3e0c279bbef4b7",
       "style": "IPY_MODEL_4b918543b8254652b8ed99453ab7a4a6"
      }
     },
     "c5654bd794a44fb5a21526a8b14de2df": {
      "model_module": "@jupyter-widgets/base",
      "model_module_version": "2.0.0",
      "model_name": "LayoutModel",
      "state": {}
     },
     "cfdf93d4c0bd42309564153842c9fcd5": {
      "model_module": "@jupyter-widgets/controls",
      "model_module_version": "2.0.0",
      "model_name": "HTMLModel",
      "state": {
       "layout": "IPY_MODEL_c5654bd794a44fb5a21526a8b14de2df",
       "style": "IPY_MODEL_672709e6bc2849b19c937ae969848199",
       "value": " 3.65M/3.65M [00:01&lt;00:00, 5.08MB/s]"
      }
     },
     "d48d586016de46649a3f817d353cc3c7": {
      "model_module": "@jupyter-widgets/controls",
      "model_module_version": "2.0.0",
      "model_name": "HTMLStyleModel",
      "state": {
       "description_width": "",
       "font_size": null,
       "text_color": null
      }
     },
     "db8f8014959541348463652b4e001e87": {
      "model_module": "@jupyter-widgets/controls",
      "model_module_version": "2.0.0",
      "model_name": "ProgressStyleModel",
      "state": {
       "description_width": ""
      }
     },
     "dc87dc90f01a4c638216e0a846d4d0e3": {
      "model_module": "@jupyter-widgets/controls",
      "model_module_version": "2.0.0",
      "model_name": "HTMLModel",
      "state": {
       "layout": "IPY_MODEL_0ea972ed8b7b4529b6930d8b8ef9293c",
       "style": "IPY_MODEL_8a8d0084847c4685bf4a701be58c46bd",
       "value": "fonts/simfang.ttf: 100%"
      }
     },
     "dfee1d76cb634c8ba65009e1b92ee02a": {
      "model_module": "@jupyter-widgets/controls",
      "model_module_version": "2.0.0",
      "model_name": "ProgressStyleModel",
      "state": {
       "description_width": ""
      }
     },
     "e03b677c6e24470d8cee9d64dc2c69a3": {
      "model_module": "@jupyter-widgets/base",
      "model_module_version": "2.0.0",
      "model_name": "LayoutModel",
      "state": {}
     },
     "ee15278fd6d24269b98ce6d85b4c4fe2": {
      "model_module": "@jupyter-widgets/base",
      "model_module_version": "2.0.0",
      "model_name": "LayoutModel",
      "state": {}
     },
     "f374ed656ddf4d8b9eca24b67cf9c5f1": {
      "model_module": "@jupyter-widgets/controls",
      "model_module_version": "2.0.0",
      "model_name": "HTMLStyleModel",
      "state": {
       "description_width": "",
       "font_size": null,
       "text_color": null
      }
     },
     "f86690c9242b463794e65c8334bb6ce6": {
      "model_module": "@jupyter-widgets/controls",
      "model_module_version": "2.0.0",
      "model_name": "HBoxModel",
      "state": {
       "children": [
        "IPY_MODEL_964a26ddbd404e18b94b65a0b3cd0316",
        "IPY_MODEL_0bfb46a2b88548369925863e9038270e",
        "IPY_MODEL_135b0d19c276461ea52c1f36deb1ba78"
       ],
       "layout": "IPY_MODEL_21162005d2944e12b340e2313e797345"
      }
     }
    },
    "version_major": 2,
    "version_minor": 0
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
